import { TriggerOpTypes } from "@vue/reactivity";
import { isArray, isIntegerKey } from "../common/utils";
/**
 * 
 * @param fn 
 * @param effect 
 * @returns 
 * 前端代码：effect(() => {data.name;data.age})
 * effect中的属性(name,age)，都会收集effect
 * 当属性的值发生改变，会重新执行effect
 */
export function effect(fn, options: any = {}) {
  // effect替代了vue2.0版本中的watcher,是在effect中 收集依赖，数据变化时 更新视图
  // 将effect变成响应式的，以实现数据更新视图变化
  const effect = createReactiveEffect(fn, options);
  if (!options.lazy) {
    effect(); // 在option中没有lazy的时候，响应式的effect会执行一次
  }
  return effect;
}

let uid = 0;
let activeEffect; //存储当前的effect
let effectTrack = [];
// 实现了effect执行和当前的属性进行了关联
function createReactiveEffect(fn, options) {
  const effect = function reactiveEffect() {
    if (!effectTrack.includes(effect)) { // 保证effect没有加入到effectTrack中
      try {
        effectTrack.push(effect);
        activeEffect = effect;
        return fn(); // 函数执行时会取值，会调用get方法
      } finally {
        // 执行完之后 让effect出栈
        effectTrack.pop();
        activeEffect = effectTrack[effectTrack.length - 1];
      }
    }
  }
  effect.uid = uid++; //制作一个effect的标识，区分effect
  effect._isEffect = true; //用于标识这个是响应的effect
  effect.raw = fn; //保留effect对应的原函数
  effect.options = options; //在effect上保存用户属性
  return effect;
}

// 让某个对象中的属性 收集当前她对应的effect函数，角色和vue2.0的watcher一样，都是收集依赖的
let targetMap = new WeakMap(); // 用来存储target
export function track(target, type, key) {
  /* 
  * target:{name:'lisa',age:18}
  * name:属性
  * effect(() =>{
  *  return `${target.name}`
  * })
  * {name:'lisa',age:18} ==> name==>多个effect([effect,effect...])(可能name属性在多个effect中被调用)
  * 最终需要转成weakMap对象 key => {name:'lisa',age:18} value (map) => {name=>set,age=>set}
  */
  // track函数做到的就是，收集到某个对象(target)下的某个属性(key)对应的某个(effect函数)
  //activeEffect; //表示的是 当前正在运行的effect
  if (activeEffect === undefined) { // activeEffect为undefined 说明没有effect,不用收集依赖(也就是如果属性在effect中使用，就肯定会有activeEffect)
    return;
  }
  let depsMap = targetMap.get(target); //depsMap里存放的是：target:{name:'lisa',age:18}
  if (!depsMap) {// 如果targetMap中没有target 使用set设置
    targetMap.set(target, (depsMap = new Map));
  }
  let dep = depsMap.get(key);
  if (!dep) {  //depsMap里存放着当前key值是否有对应的set value (map) => {name=>set} 也就实现了name对应了一个set
    // 每次取值，都要进行依赖收集 如果一个effect回调(effect(() => {data.name;data.name}))中存在多次取值，
    // 就会存在多次收集(也就是一个effect会被收集多次)，而这个收集是没有意义的，所以这个使用Set可以进行去重
    depsMap.set(key, (dep = new Set));
  }
  if (!dep.has(activeEffect)) { //在判断这个set里 是否有对应的effect 如果没有 就给当前的set 设置effect(activeEffect)
    dep.add(activeEffect);
  }
  console.log(target)
  /**
   * weakMap {
   *  key:{name:'lisa',age:18}
   *  value: Map(2){'name' => Set(1),'AGE' =>Set(2)}
   * }
   * Map {
   *   key: 'name';
   *   value: Set(2) {f,f} //f就是当前的effect函数
   * }
   */
}

// trigger的作用就是找到属性对应的effect,让其执行(数组、对象)
export function trigger(target, type, key?, newValue?, oldValue?) {
  // trigger存放的是更新逻辑
  // 判断targetMap中是否收集过effect,没有收集过就不处理
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  // 将所有的effect收集到数组中，之后依次执行
  let effects = new Set();
  const add = (effectToAdd) => { //effectToAdd可能有多个，遍历添加set
    effectToAdd.forEach((effect) => effects.add(effect));
  }
  //1、修改数组长度，对数组的影响会很大
  if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key > newValue) { //如果更改的长度，小于收集到的索引，那么这个索引也需要触发effect 重新执行
        add(dep)
      }
    })
  } else {
    // 对象
    if (key !== undefined) {
      add(depsMap.get(key)) //把key对应的effect取出来添加set属性
    }
    // 如果修改数组中的索引，就应该要触发长度的更新
    switch (type) {
      case TriggerOpTypes.ADD:
        if (isArray(target) && isIntegerKey(key)) {
          add(depsMap.get('length'));
        }
    }
  }
  effects.forEach((effect: any) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  });
}
